/*
 * Copyright 2008  Reg Whitton
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.java.dev.eval;

class Compiler
{
    private final Tokeniser tokeniser;

    Compiler( String expression )
    {
        this.tokeniser = new Tokeniser( expression );
    }

    Operation compile( )
    {
        Object expression = compile( null, null, 0, ( char )0, -1 );

        /*
         * If expression is a variable name or BigDecimal constant value then we need to put into a NOP operation.
         */
        if( expression instanceof Operation )
        {
            return ( Operation )expression;
        }
        return Operation.nopOperationfactory( expression );
    }

    private Object compile( Object preReadOperand, Operator preReadOperator, int nestingLevel, char endOfExpressionChar, int terminatePrecedence )
    {
        Object operand = preReadOperand != null ? preReadOperand : getOperand( nestingLevel );
        Operator operator = preReadOperator != null ? preReadOperator : this.tokeniser.getOperator( endOfExpressionChar );

        while( operator != Operator.END )
        {
            if( operator == Operator.TERNARY )
            {
                Object operand2 = compile( null, null, nestingLevel, ':', -1 );
                Object operand3 = compile( null, null, nestingLevel, endOfExpressionChar, -1 );
                operand = Operation.tenaryOperationFactory( operator, operand, operand2, operand3 );
                operator = Operator.END;
            }
            else
            {
                Object nextOperand = getOperand( nestingLevel );
                Operator nextOperator = this.tokeniser.getOperator( endOfExpressionChar );
                if( nextOperator == Operator.END )
                {
                    /* We are at the end of the expression */
                    operand = Operation.binaryOperationfactory( operator, operand, nextOperand );
                    operator = Operator.END;
                    if( preReadOperator != null && endOfExpressionChar != 0 )
                    {
                        /* The bracket also terminates an earlier expression. */
                        this.tokeniser.pushBack( Operator.END );
                    }
                }
                else if( nextOperator.precedence <= terminatePrecedence )
                {
                    /*
                     * The precedence of the following operator effectively brings this expression to an end.
                     */
                    operand = Operation.binaryOperationfactory( operator, operand, nextOperand );
                    operator = Operator.END;
                    this.tokeniser.pushBack( nextOperator );
                    operator = Operator.END;
                }
                else if( operator.precedence >= nextOperator.precedence )
                {
                    /* The current operator binds tighter than any following it */
                    operand = Operation.binaryOperationfactory( operator, operand, nextOperand );
                    operator = Operator.END;
                    operator = nextOperator;
                }
                else
                {
                    /*
                     * The following operator binds tighter so compile the following expression first.
                     */
                    operand = Operation.binaryOperationfactory( operator, operand, nextOperand );
                    operator = this.tokeniser.getOperator( endOfExpressionChar );
                    if( operator == Operator.END && preReadOperator != null && endOfExpressionChar != 0 )
                    {
                        /* The bracket also terminates an earlier expression. */
                        this.tokeniser.pushBack( Operator.END );
                    }
                }
            }
        }
        return operand;
    }

    private Object getOperand( int nestingLevel )
    {
        Object operand = this.tokeniser.getOperand( );
        if( operand == Tokeniser.START_NEW_EXPRESSION )
        {
            operand = compile( null, null, nestingLevel + 1, ')', -1 );
        }
        else if( operand instanceof Operator )
        {
            /* Can get unary operators when expecting operand */
            return Operation.unaryOperationfactory( ( Operator )operand, getOperand( nestingLevel ) );
        }
        return operand;
    }
}
